<?php
/*
 * kernel/object.base.php
 *
 * This code is distributed and used under the GNU GPL v2 License,
 * please, read file LICENSE, which is situated in root directory,
 * or in "LICENSE" directory with most acceptable translation for you
 *
 * Base CMS object
 * Copyright (C) 2010, Taras Kudla
 *
 */

/*
 * Deprecated object
 */

# main events handling object 
final class gCms extends gKernel{
    # html! headers array (like css,js files,etc.)
    private $htmlheaders = array();
    # global calls "road" array 
    private $Way = array();
    # output buffering for modules
    private $ob_active = NO;
    # calculated hashes cache
    private $hashcache = array();
    # loaded module functions
    private $loadfunctions = array();
    # application 3rd party modules
    private $appmodules = array();
    # loaded modules
    private $loadmodules   = array();
    # loaded modules permissions table
    private $loadmodulesperm = array();    
    # output areas names
    private $OutKeys = array();
    # output areas values by names
    private $OutValues = array();
    # module method output data
    private $OutData	= array(); # action, data. md5(data)
    # counter of hash calculations
    public $HashCounter = 0;
    # globall calling action ( as hash )
    public $globalcall = null;
    # cms call
    private $call = array();
    # module method name - alias - pattern
    private $aliasPattern = "";
    # html page title
    public $htmltitle = null;
    
    # kernel object initialization
    public function init(){
                $this->aliasPattern = "/^([A-z0-9_]{1,100})-([A-z0-9-_]{1,230})$/";
		$this->core->setsighandler(_SIG_REDIR,array(&$this,"redirect"));
    }
    # getting call params
    public function getcall(){
    	return $this->call;
    }
      
    # listening to events
    public function listen(){
    	$this->eventListener();
    }
    # listening to events    
    public function eventListener(){
        if($this->core->get("runmode")==_CONSOLE_RUNMODE || $this->core->isconsole() ){
      		print "Method base-eventlistener can not be run in console mode.\n";
      		return false;
      	}
      	$this->core->session->void();
        $this->core->session->start();
      	$this->core->user->void();
        # system language checking
        if ( $this->core->session->exists("language")){
        	$language = $this->core->session->get("language");
        	$this->core->set("language",$language);
        }
        $this->core->DEBUG("base", "Starting EVENT_LISTENER...");
        $this->OutputKeys();
		$querydata=$this->core->router->parse();
		$this->call= $querydata;

		if(!$querydata || $querydata["module"]==null ){
			/*
			 * TODO:
			 */
		} else {
			# building action call for main event
			if( $querydata["action"]==null)$querydata["action"]="defaultaction";
			$this->globalcall=$this->GetHash($querydata["module"],$querydata["action"]);
			//array_push($calls,$this->globalcall);
		}
		# checking for params array
		if( is_array($querydata["paramsarray"])){
			$_GET=$querydata["paramsarray"];
		}
		if( $this->globalcall!=null ){
			$this->GrabWay();
		}
		$calls = $this->actionsbycall($querydata["module"],$querydata["action"]);

                $this->core->debug("base","action call stack build");
		foreach($calls as $k=>& $v){
			$this->LoadFunction($v);
		}
		$this->core->debug("base","all actions loaded");
      }

      /*
       * Get all actions to load list by call
       */
      public function actionsbycall($module,$action){

          /*
           * TODO: functions with non-main position on direct call should be
           * displayed on main position
           */
	  $actions = array($this->GetHash($module,$action));
	  # getting all functions to auto load in current level
	  $query = $this->core->dbo->select("actions as f,levellinks as l","f.alias","where f.runwith='-1'
		and l.level='".$this->core->get("accesslevel")."' and f.hash=l.hash ",_RESULT_LIST);
	  $actions = array_merge($actions,$query);
	  # getting all with current module
	  if( $module!=null){
	    $query = $this->core->dbo->select("actions as f,levellinks as l","f.alias","where f.withmod='".$module."'
      		and l.level='".$this->core->get("accesslevel")."' and f.hash=l.hash ",_RESULT_LIST);
	    $actions = array_merge($actions,$query);
	  }
	  # getting all connected
	  $query = $this->core->dbo->select("actions as f,levellinks as l","f.alias","where f.runwith
	    IN ('".implode("','",$actions)."')
	    and l.level='".$this->core->get("accesslevel")."' and f.hash=l.hash ",_RESULT_LIST);
	  $actions = array_merge($actions,$query);

	  return $actions;
      }



/*
 * Building actions and links
 */ 
    # action link alias
    public function actionlink($function=null,$params=null,$runfile=null,$mod=null){
    	return $this->GetActionLink($function,$params,$runfile,$mod);
    }
    # 
    public function htmlactionlink($action,$params=null,$value=null,$optionalhtml=null,$runfile=null){
        return $this->BuildActionLink($action,$params,$value,$optionalhtml,$runfile);
    }
    # building action link, just url or js method in ajax mode
    public function GetActionLink($function=null, $params = null,$runfile=null,$mod=null){
        if( $function==null){
            return $this->core->router->createlink($mod,null,$params,$runfile);
        }
        /*
         * TODO: Saved for back compatibility
         */
        if( preg_match($this->aliasPattern,$function,$tmp)){
            $mod = $tmp[1];
            $function = $tmp[2];
        }
        if( $mod==null && $this->core->Getenvlayer() == _ENV_MOD ){
            $mod = $this->core->getenvcaller("MOD");
        }
        $hash = $this->GetHash($mod,$function);
        /*
         * Checking for permissions
         */
        if (!$this->checkHash($hash)){
            return null;
        }
        return $this->core->router->createlink($mod,$function,$params,$runfile);
	}
	# building html a link
	public function BuildActionLink($action,$params=null,$value=null,$optionalhtml=null,$runfile=null){
            return "<a href=\"".$this->GetActionLink($action,$params,$runfile)."\" $optionalhtml >".$value."</a>";
	}
/*
 * Hashes
 */
	# checking, if user has access bu hash
	public function checkHash($hash){
		return (bool) $this->core->user->checkaccess($hash);
	}
	# create hash by module and method
	public function GetHash($a, $b = null){
		$this->HashCounter++;
		$s = $a . strtolower($b);
		if (isset($this->hashcache[$s])){
			return $this->hashcache[$s];
		} else {
			$_ = substr(md5($s), 0, _HASH_LENGTH);
			$this->hashcache[$s] = $_;
			return $_;
		}
	}
/*
 * Aliases
 */	
	# building alias
	public function getalias($mod,$function){
		return $mod."-".$function;
	}

/*
 * Road, way TODO: It's ugly
 */
	# build way for a call recursively
	private function WayRecursion($id, $lt){
		if ($id == 0)
			return null;
		$q = $this->core->dbo->select("langterms", "parent,term,module,method,value", " where id='" . $id . "' ");
		$this->Way[] = array(
			"a" => $q[0]["method"],
			"m" => $q[0]["module"],
			"v" => $q[0]["value"]
		);
		$lt++;
		if ($q[0]["parent"] != 0)
			$this->WayRecursion($q[0]["parent"], $lt);
	}
	# parsing way for a parent call
	public function grabparentway($method,$module=null){
		$module = ($module == null && $this->core->getenvlayer()==_ENV_MOD)?$this->core->getenvcaller("MOD"):$module;
		return $this->grabway($this->GetHash($module,$method));
	}	
	
	# parsing way by a global call
	public function GrabWay($hash=null){
		$hash = ($hash==null)? $this->globalcall:$hash;
		$q = $this->core->dbo->select("langterms", "id,value", " where term='" . $hash . "' and lang='" . $this->core->language() . "' order by parent desc ");
		if (count($q) == 0)
			return null;
		$this->WayRecursion($q[0]["id"], 0);
		$this->Way = array_reverse($this->Way);
	}
    # getting way array
	public function getway(){
		return $this->Way;    
	}
    # building way links and returning as string
	public function BuildWay(){
		$t = "";
		$c = count($this->Way);
		foreach ($this->Way as $k => & $v){
			if (!isset($v["a"]))
				$v["a"] = "null";
			/*
			 * TODO:
			 */
			$t .= ($k != ($c) ? "&nbsp;>&nbsp;" : null)."<a href=\"".$this->GetActionLink($v["a"], (isset($v["p"]) ? $v["p"] : null),null,$v["m"]) ."\">".$v["v"]."</a>&nbsp;";
		} 
		return  ($c==0?null:"<a href=\"" . $this->getactionlink() . "\">" . $this->core->i18n->get("home") . "</a>").$t;
	}
	# adding new way link
	public function AddWay($method, $params = null, $value = null,$module=null){
		if( $module == null && $this->core->getenvlayer()==_ENV_MOD){
			$module = $this->core->getenvcaller("MOD");
		}		
		$this->Way[] = array(
			"a" => $method,
			"m"	=> $module,
			"p" => $params,
			"v" => $value
		);
		return true;
	}
/*
 * System title on way parsing
 */	
	public function gettitle(){
		$delimiter = "::";
		$title = null;
		$c = count($this->Way);
		foreach( $this->Way as $k=>$v){
			$title .= $delimiter.$v["v"];
		}
		return $title;
	}
/*
 * Getting ob state
 */
	public function getobstate(){
		return $this->ob_active;
	}
	
/*
 * Redirection
 */
	public function redirect($alias= null,$params = null){
		return $this->sendHeader($alias,$params);
	}
      
	public function sendHeader($action = null, $params = null){
		# storing current runmode
		$tmp_rl = $this->core->get("runmode");
		# chcking for hash, if null - link eq default start page
		if ($action == null || $action=="null"){
			$link = $this->getactionlink();
		}else if(!preg_match("/^([A-z0-9_]{2,6}):\/\//s",$action) ){
			$this->core->set("runmode",_HTML_RUNMODE);
			$link = $this->GetActionLink($action, $params);
			if (!$link){
				$this->core->error("BASE-REDIRECT", "Cannot redirect, action '$action' is broken or not permitted",_ERR_FATAL,__FILE__,__LINE__);
				return null;
			}
		} else {
			$link = $action;
		}
		# ajax run mode handler or if i'm running throw api
		$this->core->set("runmode",$tmp_rl);
		if ($this->core->get("runmode") == _AJAX_RUNMODE ){
			# TODO: ajax after rebuild and message on error
			ob_end_clean();
			$this->core->api->CallRedirect($link);
			$this->core->signal(_SIG_TERM);
		}
		# closing all alive connections and writing session
		$this->core->signal(_SIG_CLSCON);
		# closing all output buffers
		if ($this->ob_active){
			ob_end_clean();
		}
		# getting environment caller data and checking for errors
		$env = $this->core->getenvcaller();  
		if( $env["ERROR_COUNT"]>0){
			print "Redirect action stopped. Caller have some errors. Please, check log files. User error: ".$env["ERROR"];
			if( !$this->core->isconsole())
				print "<hr>Debugger dump:<br><hr>".nl2br($this->core->getdebug(_STRING_));
			
		} else {
			# redirection
			header("location: ".$link);
		}
		print "Should redirect to: ".$link.", if you see this text something gone wrong.";
		$this->core->signal(_SIG_TERM);
	}
/*
 * Actions, modules
 */
    public function & callaction($module,$method=null,$parameters=null){
    	/*
    	 * Main action caller, note: without any permission checks
    	 */
    	$false=false;
    	if( !$this->core->sigcalled(_SIG_INIT)){
    		$this->core->error("callaction","Cannot work without system initialization (_sig_init)",_ERR_FATAL,__FILE__,__LINE__);
    		return $false;
    	}
    	if( !is_array($parameters)){
    		$parameters=array();
    	}
    	if( preg_match($this->aliasPattern,$module,$tmp)){
    		$module = $tmp[1];
    		$method = $tmp[2];
    	}
    	if( $method == null ) {
    		$method = "defaultaction";
    	}
    	$hash = $this->GetHash($module,$method);
    	# checking for permission
    	if( $this->core->isloaded("user") && !$this->core->user->checkaccess($hash)){
		$this->core->error("callaction","Permission denied",_ERR_NOTICE,__FILE__,__LINE__);
    		return $false;
    	}
    	# preparing environment
        $this->core->setenvlayer(_ENV_MOD);
        $this->core->setenvcaller(array(
        		"ID" 	=> $hash,
        		"HASH"	=> $hash,
        		"NAME"	=> $method,
        		"MOD"	=> $module,
        		"ERROR"	=> null,
        		"ERROR_COUNT"	=>0,
        		"CALLSTACK"	=> array()
        ));
    	#loading basic module
    	$modobj=$this->callmodule($module);
    	if( !$modobj)
    		return $false;
        # preparing output buffering
    	$this->ob_active = YES;
        ob_start();
        $buffer = null;
        try {
            $buffer = call_user_func_array(array(&$modobj,$method),$parameters);
        }
        catch (GepardException $error) {
          	$this->core->updatenvcaller("error",$error->getMessage());          	
        }
        $bufferOutput = ob_get_contents();
        ob_end_clean();
        $this->ob_active = NO;
        #prepending all output in ob
        if( !is_string($buffer) && !is_null($buffer)){
        	
        } else {
        $buffer = $bufferOutput.$buffer;
        $env = $this->core->getenvcaller();     
        if (!empty($env["ERROR"])) {
            $buffer = $env["ERROR"] . $buffer;
        }
        }
    	$this->core->envclear();
    	return $buffer;    
    } 

   # loading global call function
   public function LoadFunction($action,$onlyme=NO)
      {
        # checking for action format
        if( preg_match($this->aliasPattern,$action,$tmp)){
        	$hash = $this->GetHash($tmp[1],$tmp[2]);
        }
		else if( preg_match("/^([A-z0-9]{"._HASH_LENGTH."})$/",$action) ){
			$hash = $action;
		}
        else {
        	/*
        	 *  Action format incorrect, error
        	 */
        	return false;
        }        
        # if action was previously loaded        
        if (in_array($hash, $this->loadfunctions)){
			/*
			 * Action was previously loaded
			 */
            return false;
        }
        # loading basic information
        $Query = $this->core->dbo->select("actions", "id,action,module,runmode,position,
        guest,alias,weight",
        "  where hash='".$this->core->escapeString($hash)."' limit 1 offset 0",_RESULT_ROW);
        if(count($Query)==0){
        	return false;
        }
        # checking for permission to load action 
        if ( (!$this->checkHash($hash) && !(bool)$Query["guest"]) || count($Query)==0 )
          {
            /*
             * Permission denied or action not found
             */
            return false;
          }
        #  
        array_push($this->loadfunctions,$hash);
        list($module,$function,$alias,$weight) = array($Query["module"],$Query["action"],$Query["alias"],$Query["weight"]);
        # set environment        
        $this->core->setenvlayer(_ENV_MOD);
        $this->core->setenvcaller( array(
        		"ID" 	=> $hash,
        		"HASH"	=> $hash,
        		"NAME"	=> $function,
        		"MOD"	=> $module,
        		"ERROR"	=> null,
        		"ERROR_COUNT"=>0,
        		"CALLSTACK"	=> array()
        ) );
        $tmp = null;
        # loading module
        $tmp=$this->callmodule($module);
        
        $QUERIES_COUNTER = 0;//$this->core->dbo->counter;
		 # starting time        
        $MEMUSAGE = memory_get_usage();
        #Output Data Buffering
        $this->ob_active = YES;
        ob_start();
        $buffer = null;
        try {
            $buffer = $tmp->$function();
        }
        catch (GepardException $error) {
          	$this->core->updateenvcaller("error",$error->getMessage());          	
        }
        $bufferOutput = ob_get_contents();
        ob_end_clean();
        $this->ob_active = NO;	
        #prepending all output in ob
        $buffer = $bufferOutput . $buffer;		
        $env = $this->core->Getenvcaller();     
        if (!empty($env["ERROR"]))
          {
            $buffer = $env["ERROR"] . $buffer;
          }
        if ($onlyme == YES)
          {
            return $buffer;
          }
        if ($this->core->get("runmode") == _AJAX_RUNMODE && !$this->core->api->A_LISTENER)
          {
            $buffer = "<div id='function_" . $Query["id"] . "'>" . $buffer . "</div>";
          }
        else if ($this->core->get("runmode") == _AJAX_RUNMODE && $this->core->api->A_LISTENER)
          {
            $buffer = "<item id='" . $Query["id"] . "'><![CDATA[ <div id='function_" . $Query["id"] . "'>" . $this->core->view->MakeReplaces($buffer) . "</div> ]]></item>";
          }
        /*
         * Clearing caller data
         */
        $this->core->envclear();
        /*
         * Output Areas Processing
         */
        $QUERIES_COUNTER=0;//$this->core->dbo->counter-$QUERIES_COUNTER;
        if (isset($this->OutKeys[(int)$Query["position"]]))
          {
            $value = $this->OutKeys[(int)$Query["position"]];			            
            $this->OutValues[$value][$alias]=(int)$weight;            
            $this->OutData[$alias]=array("data"=>&$buffer,"md5"=>md5($buffer));
          }
        else
          {
            $this->core->DEBUG("BASE", "Cannot found output area for function: " . $alias);
          }
        # updating function information
        $this->core->dbo->update("actions","set memuse='".(($MEMUSAGE-memory_get_usage())*(-1))."', extime='0' where alias='$alias'");
          
        $this->core->debug("base-actloader","action '$alias' loaded");
        //$this->LoadConnectedFunctions($hash);
        return true;
      }

	# creating module object
	public function & callmodule($name){
		$ref = false;
		$path = $this->core->parseasdir($this->core->get("modsdir"));
                $appmodsfile = $this->core->parseasdir($this->core->get("appmodsdir")).$name."/mod.".$name.".php";
		$file = $path.$name."/mod." . $name . ".php";
		if (isset($this->loadmodules[$name]) && is_object($this->loadmodules[$name]) 
							&& get_class($this->loadmodules[$name]) == "mod_" . $name){
			$object = $this->loadmodules[$name];
			return $object;
		} else {
                        if( !file_exists($file)){
                            if( !file_exists($appmodsfile)){
                                $this->core->error("callmodule", "Cannot find module '" . $name . "'",_ERR_FATAL,__FILE__,__LINE__);
                                return false;
                            } else {
                                $file = $appmodsfile;
                                array_push($this->appmodules, $name);
                            }
                        }
			require_once($file);
			$classname = "mod_" . $name;
			if (get_parent_class($classname) == _DEFAULT_ABSTRACT_MODULE || get_parent_class($classname)=="gmodule" ){
			# loading module for first time
			# switching to custom errors handling
			$tmp = $this->core->get("exceptiononerror");
			$this->core->set("exceptiononerror",true);
			try {
                            $data = $this->core->dbo->select("modules","permissions","where modname='$name'",_RESULT_SINGLE);
                            if( !is_string($data))$data=false;
			} catch ( GepardException $e){
                            print $e->getMessage();
                            $data=false;
			}
			$this->core->set("exceptiononerror",$tmp);
 			if( is_bool($data) ){
                            /*
                             * FIXME: default or full permissions, i don't know what is better
                             */
                            $permissions=array(_PERM_WRITESQL=>true,_PERM_READSQL=>true,_PERM_WRITE=>true,_PERM_ENV=>true,_PERM_CHG=>true,_PERM_CALL=>true);
			} else {
                           if( empty($data))
                                $data=_PERM_DEFAULT;
                           $permissions=array(
                           _PERM_WRITESQL=>isset($data[0])?(int)$data[0]:false,
                           _PERM_READSQL=>isset($data[1])?(int)$data[1]:false,
                           _PERM_WRITE=>isset($data[2])?(int)$data[2]:false,
                           _PERM_ENV=>isset($data[3])?(int)$data[3]:false,
                           _PERM_CALL=>isset($data[4])?(int)$data[4]:false,
                           _PERM_CHG=>isset($data[5])?(int)$data[5]:false);
			}
			$this->loadmodulesperm[$name]=$permissions;					
			$this->loadmodules[$name] = new $classname($this->core);
			$this->loadmodules[$name]->init();
			return $this->loadmodules[$name];

			} else {
                            $this->core->error("callmodule","Module '$classname' format is unsupported",_ERR_NOTICE,__FILE__,__LINE__);
                        }
		$this->core->debug("callmodule","Module '$name' loaded");
		}         
		return $ref;  
	}

	# checking if module is in application modules directory
	public function checkappmod($modname){
	    return in_array($modname, $this->appmodules);
	}

	# getting already compiled module object
	public function & getmodobj(){
		$null;
		if( $this->core->getenvlayer() == _ENV_MOD){
			return $this->loadmodules[$this->core->getenvcaller("mod")];
		}
		return $null;
	}	
	
	# checking module permissions
	public function checkmodpermission($permission,$modname=null){
		# checking environment
		if( $this->core->getenvlayer()!=_ENV_MOD){
			return true;
		}
		if( $modname==null)
			$modname = $this->core->getenvcaller("MOD");	
		if( array_key_exists($modname,$this->loadmodulesperm)){
			return (bool)$this->loadmodulesperm[$modname][$permission];  
		}else{
			return false;
		} 	
	}
	
/*
 * Output areas
 */      
	
    # building output keys
    public function OutputKeys(){
        $q = $this->core->dbo->select("output_areas", "name,value", " limit 20 offset 0");
        $i = 0;
        while ($i < count($q)){
            $this->OutKeys[$q[$i]["name"]]    = $q[$i]["value"];
            $this->OutValues[$q[$i]["value"]] = array();
            $i++;
        }
        return true;
    }
    # getting just output areas values
    public function & getoutvalues(){
        return $this->OutValues;
    }
    # getting all output data by areas
    public function & getoutdata(){
        return $this->OutData;
    }

    /*
     *  HTML headers, external js,css
     */
	
    # adding js file
    public function addjs($file){
    	$this->addhtmlheader("js",$file);
    }
    # adding js code directly
    public function addjscode($code){
    	$this->htmlheaders[]="<script language=\"javascript\">$code</script>";
    }
    # adding css file
    public function addcss($file){
    	$this->addhtmlheader("css",$file);
    }   
	# adding just as line
    public function addhtmlheaderline($string){
    	$this->htmlheaders[]=$string;
    }
    
    # adding html header with types checking ( its ugly way :) )
	public function addhtmlheader($type, $value){
		# checking for mod url prepends
		if ($this->core->getenvlayer() == _ENV_MOD && !preg_match("/^\/.+$/",$value)){
			$mod = $this->core->getenvcaller("MOD");
			if( $this->checkappmod($mod)){
			    $way =  $this->core->view->getappmodsurl(). $mod. "/" . $value;
			} else {
			    $way =  $this->core->view->getmodsurl(). $mod. "/" . $value;
			}
			
		} else {
			$way = $value;
		}
		# switching by types
		switch ($type){
			# for css files
			case "css":
				$this->htmlheaders[] = "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . $way . "\" >";
			break;
			# for javascript files
			case "js":
				$this->htmlheaders[] = "<script language=\"javascript\" type=\"text/javascript\" src=\"" . $way . "\" ></script>";
			break;
			# for html page title
			case "title":
				$this->htmlheaders[] = "<title>$value</title>";
			default:
			return false;
			break;
		}
		return true;
	}
	# getting all declared html headers
	public function GetHTMLHeaders(){
		# system api declarations, etc.
		$head = "<script language=\"javascript\" src='" . $this->core->view->getjscoreurl() . "jquery.js' type='text/javascript' ></script>\n";
		$head .= "<script language=\"javascript\" type='text/javascript' >var ___='" .$this->core->url(). "'; var ____='" . $this->core->apifile() . "';</script>\n";
		$head .= "<script language=\"javascript\" src='" .$this->core->view->getjscoreurl(). "gepard.js' type='text/javascript' ></script>\n";
		# user added html headers
		foreach ($this->htmlheaders as $k => & $v){
			$head .= $v . "\n";
		}
		return $head;
	}
}